<?php
// +----------------------------------------------------------------------
// | 通用服务层
// +----------------------------------------------------------------------
// | 主要服务于API接口基类
// +----------------------------------------------------------------------
// | 错误编码头 202xxx
// +----------------------------------------------------------------------
namespace common\service;

use think\model\RelationModel;
use think\module\Module;
use support\ErrorCode;
use think\Exception;
use think\Hook;
use Throwable;

class CommonService
{
    // model对象
    /** @var RelationModel */
    protected $modelObject;

    // 当前模块编码
    protected $currentModuleCode;

    // 当前模块关联结构
    protected $currentModuleLinkRelation = [];

    // 哪些模块外键为link_id的
    protected $moduleForeignKeyIsLinkId = ['follow', 'timelog'];

    // 错误码
    protected $errorCode = 0;

    protected $errorMessage = "";

    /**
     * @var \common\service\mediaService
     */
    protected $mediaService;

    /**
     * @var object 对象实例
     */
    protected static $instance = [];


    /**
     * CommonService constructor.
     * @param string $class
     * @throws Exception
     */
    public function __construct($class = null)
    {
        // 实例化当前model
        if (class_exists($class)) {
            $this->modelObject = new $class();
        } else {
            throw new Exception("Class does not exist.", ErrorCode::CLASS_DOES_NOT_EXIST);
        }
        $this->mediaService = MediaService::getInstance();
    }

    /**
     * 重置错误信息
     * @param string $errorMessage
     */
    public function setErrorMessage(string $errorMessage): void
    {
        $this->errorMessage = $errorMessage;
    }

    /**
     * 重置默认值
     */
    private function setDefaultOptions()
    {
        $this->setErrorCode(0);
        $this->setErrorMessage('');
    }

    /**
     * 初始化
     * @param string $moduleName
     * @return CommonService
     * @throws Exception
     */
    public static function instance($moduleName = '')
    {
        $class = '\\common\\model\\' . string_initial_letter($moduleName) . 'Model';
        if (!empty($moduleName)) {
            if (empty(self::$instance[$class]) && !isset(self::$instance[$class])) {
                self::$instance[$class] = new static($class);
            }
            // 设置commonService 的当前模块code
            self::$instance[$class]->setCurrentModuleCode($moduleName);
        } else {
            throw_strack_exception("Module does not exist.", ErrorCode::MODULE_NOT_EXIST);
        }

        return self::$instance[$class];
    }

    /**
     *  模块code 支持传递
     *  优先级 function param > $this->currentModuleCode > request()->getModuleCode
     * @param null $currentModuleCode
     * @return string|null
     */
    public function getCurrentModuleCode($currentModuleCode = null)
    {
        if (empty($currentModuleCode)) {
            $currentModuleCode = $this->currentModuleCode ?? request()->getModuleCode();
        }

        if ($currentModuleCode == 'entity') {
            $currentModuleCode = request()->getModuleCode();
        }
        return $currentModuleCode;
    }

    /**
     * 刷新当前模块的字段缓存
     * @return void
     */
    public function flushCurrentModelFields()
    {
        $this->modelObject->flush('', true);
        trace(date("Y-m-d H:i:s") . ' ' . $this->modelObject->getModelName() . ' flushed', 'DEBUG');
    }

    /**
     * 刷新所有实例的model fields缓存
     * @return void
     */
    public static function flushModelFields()
    {
        /** @var self $item */
        foreach (self::$instance as $item) {
            $item->flushCurrentModelFields();
        }
    }

    /**
     * 处理创建自定义字段
     * @param $data
     * @param string $currentModuleCode
     * @return array
     */
    public function dealCreateCustomField($data, $currentModuleCode = ""): array
    {
        $dealData = [];

        $currentModuleCode = $this->getCurrentModuleCode($currentModuleCode);

        $currentModuleFieldAttributeCheck = [];
        foreach ($data as $field => $value) {
            if (array_key_exists($field, Module::$moduleDictData['field_index_by_code'][$currentModuleCode]['fixed'])) {
                // 固定字段
                $dealData[$field] = $value;
            } else {
                // 自定义字段
                if (array_key_exists($field, Module::$moduleDictData['field_index_by_code'][$currentModuleCode]['custom'])) {
                    // 更新json字段 增加字段值的属性检查
                    if (!ImportExcelService::getInstance()->getModuleFieldAttributeValueCheck($currentModuleCode, $field, $value, $currentModuleFieldAttributeCheck)) {
                        throw_strack_exception("attribute [$field] value [$value] not allowed", ErrorCode::ATTRIBUTE_VALUE_NOT_ALLOWED);
                    }
                    if (array_key_exists('json', $dealData)) {
                        $dealData['json'][$field] = $value;
                    } else {
                        $dealData['json'] = [$field => $value];
                    }
                }
            }
        }
        return $dealData;
    }

    /**
     * 查询一条基础方法
     * @param $param
     * @param string $moduleCode
     * @return mixed
     */
    public function find($param, $moduleCode = '')
    {
        if (!empty($moduleCode)) {
            $this->modelObject->setCurrentModuleCode($moduleCode);
        }
        $resData = $this->modelObject->findData($param);
        return $resData;
    }

    /**
     * 查询多条基础方法
     * @param $param
     * @param string $moduleCode
     * @return array
     */
    public function select($param, $moduleCode = '')
    {
        if (!empty($moduleCode)) {
            $this->modelObject->setCurrentModuleCode($moduleCode);
        }

        $resData = $this->modelObject->selectData($param);

        return $resData;
    }

    /**
     * 创建基础方法
     * @param $data
     * @param string $currentModuleCode
     * @param bool $throwExistsException
     * @return array|bool|null
     * @throws \Exception
     */
    public function create($data, $currentModuleCode = '', $throwExistsException = true)
    {
        // 处理自定义字段
        $dealData = $this->dealCreateCustomField($data, $currentModuleCode);

        // 执行新增操作
        $resData = $this->modelObject->addItem($dealData);
        if (!$resData) {
            if ($throwExistsException === false && $this->modelObject->getErrorCode() == \think\exception\ErrorCode::DATA_ALREADY_EXISTS) {
                $hookData = [
                    'old' => $this->modelObject->getCheckUniqueExitData(),
                    'new' => $dealData,
                ];
                Hook::listen('after_create_exist', $hookData);
                return $this->modelObject->getCheckUniqueExitData();
            }
            $errorCode = $this->modelObject->getErrorCode();
            if (empty($errorCode)) {
                $errorCode = ErrorCode::COMMON_CREATE_FAILURE;
            }
            // 通用创建失败错误码 001
            throw_strack_exception($this->modelObject->getError(), $errorCode);
        } else {
            // 返回成功数据
            return $resData;
        }
    }

    /**
     * 更新基础方法
     * @param $data
     * @param string $currentModuleCode
     * @param bool $throwNoDataChangeException
     * @return array|bool
     * @throws \Exception
     */
    public function update($data, $currentModuleCode = '', $throwNoDataChangeException = true)
    {
        // 处理自定义字段
        $dealData = $this->dealUpdateCustomField($data, $currentModuleCode);

        $resData = $this->modelObject->modifyItem($dealData);
        if (!$resData) {
            if ($throwNoDataChangeException === false && $this->modelObject->getErrorCode() === \think\exception\ErrorCode::NO_DATA_HAS_BEEN_CHANGED) {
                return $resData;
            }
            // 通用修改失败错误码 002
            throw_strack_exception($this->modelObject->getError(), ErrorCode::COMMON_UPDATE_FAILURE);
        } else {
            // 返回成功数据
            return $resData;
        }
    }

    /**
     * 删除基础方法
     * @param $param
     * @return array
     */
    public function delete($param)
    {
        // 获取当前模块删除主键
        $masterIdData = $this->modelObject->field('id')->where($param["param"]['filter'])->select();

        if (!empty($masterIdData)) {
            $masterIds = join(',', array_column($masterIdData, 'id'));
            $resData = $this->modelObject->deleteItem([
                'id' => ["IN", $masterIds],
            ]);

            if (!$resData) {
                // 通用删除失败错误码 003
                throw_strack_exception($this->modelObject->getError(), ErrorCode::COMMON_DELETE_FAILURE);
            } else {
                // 返回成功数据
                return ['ids' => $masterIds];
            }

        } else {
            throw_strack_exception('Delete item does not exist.', ErrorCode::DELETE_ITEM_NOT_EXIST);
        }
    }

    /**
     * 返回错误码
     * @return mixed
     */
    public function getErrorCode()
    {
        return $this->errorCode;
    }

    /**
     * 返回错误信息
     * @return string
     */
    public function getErrorMessage(): string
    {
        return $this->errorMessage;
    }

    /**
     * 设置错误码
     * @param int $errorCode
     */
    public function setErrorCode(int $errorCode)
    {
        $this->errorCode = $errorCode;
    }

    /**
     * 生成批量新增关联外键
     * @param $data
     * @param $preModule
     * @param $param
     * @return mixed
     */
    protected function generateRecursiveDataForeignKey(&$data, $preModule, $param)
    {
        if (in_array($preModule, $this->moduleForeignKeyIsLinkId)) {
            $data['link_id'] = $param['id'];
            $data['module_id'] = $param['module_data']['id'];
        } else {
            if ($param['module_data']['type'] === 'entity') {
                $data['entity_id'] = $param['id'];
                $data['entity_module_id'] = $param['module_data']['id'];
                $data['module_id'] = $param['module_data']['id'];

                //生成初始集 优先使用 initial_episode_code
                if (!isset($data['initial_episode_code']) || empty($data['initial_episode_code'])) {
                    if (!empty($param['initial_episode_code'])) {
                        $data['initial_episode_code'] = $param['initial_episode_code'];
                    } elseif (!empty($param['code'])) {
                        $data['initial_episode_code'] = $param['code'];
                    } else {
                        $data['initial_episode_code'] = "";
                    }
                }

            } else {
                if (
                    array_key_exists($preModule, $this->currentModuleLinkRelation)
                    && !empty($this->currentModuleLinkRelation[$preModule])
                    && $this->currentModuleLinkRelation[$preModule]['type'] === 'horizontal'
                ) {
                    // 判断是否为自定义字段
                    if (!empty($data['json'])) {
                        $data['json'][$this->currentModuleLinkRelation[$preModule]['link_id']] = $param['id'];
                    } else {
                        $data['json'] = [
                            $this->currentModuleLinkRelation[$preModule]['link_id'] => $param['id'],
                        ];
                    }

                } else {
                    $data[$param['module_data']['code'] . '_id'] = $param['id'];
                }
            }
        }

        return $data;
    }

    /**
     * 生成批量新增新增数据
     * @param $data
     * @param $module
     * @return array
     * @throws \Exception
     */
    protected function generateRecursiveDataAddData($data, $module)
    {
        $addData = [];

        if ($module === 'media') {
            // 媒体信息需要单独处理
            $this->mediaService->generateMediaData($data);
        }

        // 当前模块配置
        $currentModuleParam = Module::$moduleDictData['module_index_by_code'][$module];
        if ($currentModuleParam['type'] = 'entity') {
            $data['module_id'] = $currentModuleParam['id'];
        }

        foreach ($data as $filed => $value) {
            if (array_key_exists($filed, Module::$moduleDictData['field_index_by_code'][$module]['fixed'])) {
                $addData[$filed] = $value;
            } else if (array_key_exists($filed, Module::$moduleDictData['field_index_by_code'][$module]['custom'])) {
                if (array_key_exists('json', $addData)) {
                    $addData['json'][$filed] = $value;
                } else {
                    $addData['json'] = [
                        $filed => $value,
                    ];
                }
            }
        }

        return $addData;
    }

    /**
     * 保存递归处理数据
     * @param RelationModel $newModelObject
     * @param $addData
     * @return mixed
     * @throws \Exception
     */
    public function saveRecursiveData($newModelObject, $addData)
    {
        $addResult = $newModelObject->addItem($addData);

        if (!$addResult) {
            // 添加失败错误码 001
            $errorCode = !empty($newModelObject->getErrorCode()) ? $newModelObject->getErrorCode() : -202012;
            if ($errorCode === -411111) {
                // 已经存在数据
                $this->errorCode = $errorCode;
                $this->errorMessage = $newModelObject->getError();
                $hookData = [
                    'old' => $newModelObject->getCheckUniqueExitData(),
                    'new' => $addData,
                ];
                Hook::listen('after_create_exist', $hookData);
                return $newModelObject->getCheckUniqueExitData();
            } else {
                throw new \Exception($newModelObject->getError(), $errorCode);
            }
        } else {
            // 返回成功数据
            return $addResult;
        }
    }

    /**
     * 递归处理多级新增项
     * @param $resData
     * @param $item
     * @param $module
     * @param $newModelObject
     * @param $currentModuleData
     * @return array
     * @throws \Exception
     */
    protected function recursiveDataItemCreate(&$resData, $item, $module, $newModelObject, $currentModuleData)
    {
        $addData = $this->generateRecursiveDataAddData($item['data'], $module);

        $addResult = $this->saveRecursiveData($newModelObject, $addData);

        $currentModuleCode = \request()->getModuleCode();

        // 新增当前模块数据
        if ($module === $currentModuleCode) {
            $resData[] = $addResult['id'];
        }

        // 传入下个循环值
        $nextParam = [
            'module_data' => $currentModuleData,
            'id' => $addResult['id'],
        ];

        //当前模块是集 那么存入当前插入数据的code
        if ($module === "episode") {
            $nextParam['code'] = $addResult['code'] ?? "";
        }
        //继承 父级的 initial_episode_code属性
        $nextParam['initial_episode_code'] = $addResult['initial_episode_code'] ?? "";

        if (array_key_exists('after', $item) && !empty($item['after'])) {
            $this->recursiveDataCreate($resData, $item['after'], $nextParam);
        }

        return $resData;
    }

    /**
     * 递归处理多级新增
     * @param $resData
     * @param $data
     * @param array $param
     * @return mixed
     * @throws \Exception
     */
    protected function recursiveDataCreate(&$resData, $data, $param = [])
    {
        // 把上一级外键批量填入下一层
        if (!empty($param)) {
            foreach ($data as $preModule => &$preItem) {
                if (!empty($preItem[0])) {
                    foreach ($preItem as &$preAddItem) {
                        if (!empty($preAddItem['data'])) {
                            $this->generateRecursiveDataForeignKey($preAddItem['data'], $param['module_data']['pre_module'], $param);
                        }
                    }
                } else {
                    if (!empty($preItem['data'])) {
                        $this->generateRecursiveDataForeignKey($preItem['data'], $param['module_data']['pre_module'], $param);
                    }
                }
            }
        }

        // 深度遍历
        foreach ($data as $module => $item) {

            // 所属模块对象名称
            $currentModuleCode = '';
            $realModuleCode = $module;
            if (array_key_exists($module, Module::$moduleDictData['module_index_by_code'])) {
                $currentModuleCode = $module;
            } else if (array_key_exists($module, $this->currentModuleLinkRelation)) {
                $currentModuleCode = $this->currentModuleLinkRelation[$module]['module_code'];
            }

            if (!empty($currentModuleCode)) {
                $currentModuleData = Module::$moduleDictData['module_index_by_code'][$currentModuleCode];
                $currentModuleData['pre_module'] = $realModuleCode;


                if (Module::$moduleDictData['module_index_by_code'][$currentModuleCode]['type'] === 'entity') {
                    $modelObjectName = "EntityModel";
                } else {
                    $modelNamePre = Module::$moduleDictData['module_index_by_code'][$currentModuleCode]['name'];
                    $modelObjectName = "{$modelNamePre}Model";
                }

                $modelObjectClass = '\\common\\model\\' . $modelObjectName;
                $newModelObject = new $modelObjectClass();

                if (!empty($item[0])) {
                    // 多维数组

                    // 循环添加
                    foreach ($item as $addItem) {
                        // 不存在data跳入下个循环
                        if (!array_key_exists('data', $addItem)) {
                            return $resData;
                        }

                        $this->recursiveDataItemCreate($resData, $addItem, $currentModuleCode, $newModelObject, $currentModuleData);
                    }
                } else {
                    $this->recursiveDataItemCreate($resData, $item, $currentModuleCode, $newModelObject, $currentModuleData);
                }
            }
        }
    }

    /**
     * 关联创建数据
     * @param $data
     * @param $moduleCode
     * @return array
     */
    public function createRelationData($data, $moduleCode)
    {
        // 批量新增数据
        $resData = [];

        $class = get_module_model_name(Module::$moduleDictData['module_index_by_code'][$moduleCode]);
        /** @var RelationModel $objectModel */
        $objectModel = new $class();

        $objectModel->setCurrentModuleCode($moduleCode);
        $objectModel->startTrans();//强制开启事务

        $this->currentModuleLinkRelation = $objectModel->parserFilterModuleRelation(true);

        try {
            $this->recursiveDataCreate($resData, $data);
            $objectModel->commit();
            return $resData;
        } catch (Throwable $e) {
            $objectModel->rollback();
            throw_strack_exception($e->getMessage(), $e->getCode());
        }
    }

    /**
     * 通用新增处理函数，如果有多层数据逐步添加，单层数据直接添加
     * @param $param
     * @param $moduleData
     * @param bool $throwExistsException
     * @return array|bool
     * @throws \Exception
     */
    public function commonCreate($param, $moduleData, $throwExistsException = true)
    {
        $this->setDefaultOptions();
        if (check_is_relation_create($param["data"])) {
            // 多维数组走关联新增方法
            return $this->createRelationData($param["data"], $param['module_code']);
        } else {
            // 一维数组走普通新增方法

            if ($moduleData['type'] === 'entity') {
                // 实体类型增加当前模块id
                $param['data']['module_id'] = $moduleData['id'];
            }

            return $this->create($param["data"], $moduleData['code'], $throwExistsException);
        }
    }


    /**
     * 多维数组走关联新增方法 TODO
     * @param $data
     * @return array
     */
    protected function updateRelationData($data)
    {
        return [];
    }

    /**
     * 通用更新处理函数，如果有多层数据逐步更新，单层数据直接更新
     * @param $param
     * @param null $moduleData
     * @param bool $throwNoDataChangeException
     * @return array
     * @throws \Exception
     */
    public function commonUpdate($param, $moduleData = null, $throwNoDataChangeException = true)
    {
        $this->setDefaultOptions();
//        if (check_is_relation_create($param["data"])) {
//             多维数组走关联新增方法
//            return $this->updateRelationData($param["data"]);
//        } else {
        // 一维数组走普通新增方法
        return $this->update($param["data"], $moduleData['code'] ?? '', $throwNoDataChangeException);
//        }
    }

    /**
     * 不同资产批量更新相同数据
     * @param $data
     * @param $moduleData
     * @return int
     */
    public function commonBatchUpdateSame($data, $moduleData = null)
    {
        $dealData = [
            'fixed' => [],
            'json' => [],
        ];
        $tableName = get_module_table_name($moduleData);
        foreach ($data as $field => $value) {
            if (array_key_exists($field, Module::$moduleDictData['field_index_by_code'][$moduleData['code']]['fixed'])) {
                // 固定字段
                $dealData['fixed'][$field] = $value;
            } else {
                // 自定义字段
                if (array_key_exists($field, Module::$moduleDictData['field_index_by_code'][$moduleData['code']]['custom'])) {
                    if (array_key_exists('json', $dealData)) {
                        $dealData['json'][$field] = $value;
                    } else {
                        $dealData['json'] = [$field => $value];
                    }
                }
            }
        }
        if (empty($dealData['fixed']) && empty($dealData['json'])) {
            throw_strack_exception('Parameter error', ErrorCode::REQUEST_PARAM_ERROR);
        }

        $dealData['id'] = implode(',', $dealData['fixed']['id']);
        unset($dealData['fixed']['id']);
        $execute = "update {$tableName} set";
        $fixed = "";
        $json = '';
        if ($dealData['fixed']) {
            foreach ($dealData['fixed'] as $field => $value) {
                if (is_string($value)) {
                    $value = "'$value'";
                }
                if ($fixed) {
                    $fixed .= ", {$field} = {$value}";
                } else {
                    $fixed = " {$field} = {$value}";
                }
            }
            $execute .= $fixed;
        }
        if ($dealData['json']) {
            foreach ($dealData['json'] as $field => $value) {
                if ($json) {
                    $json .= ", '$.{$field}','{$value}'";
                } else {
                    $json = "json,'$.{$field}','{$value}'";
                }
            }
            if ($fixed) {
                $execute .= ", json = json_set({$json})";
            } else {
                $execute .= " json = json_set({$json})";
            }
        }
        $execute .= " where id in ({$dealData['id']})";
        return $this->modelObject->execute($execute);
    }

    /**
     * 批量更新数据
     * @param $dataList
     * @param null $moduleData
     * @param bool $throwNoDataChangeException
     * @return array
     * @throws \Exception
     */
    public function commonBatchUpdate($dataList, $moduleData = null, $throwNoDataChangeException = true)
    {
        $res = [];
        foreach ($dataList as $data) {
            $this->setDefaultOptions();
            $res[] = $this->update($data, $moduleData['code'] ?? '', $throwNoDataChangeException);
        }
        return $res;
    }

    /**
     * @param mixed $currentModuleCode
     */
    public function setCurrentModuleCode($currentModuleCode)
    {
        $this->currentModuleCode = $currentModuleCode;
        $this->modelObject->setCurrentModuleCode($currentModuleCode);
        return $this;
    }

    /**
     * 处理更新自定义字段
     * @param $data
     * @param string $currentModuleCode
     * @return array
     * @throws Exception
     */
    public function dealUpdateCustomField($data, $currentModuleCode = ""): array
    {
        $pk = $this->modelObject->getPk();

        if (!isset($data[$pk])) {
            throw_strack_exception("missing pk in data", ErrorCode::MISS_PK_IN_DATA);
        }

        $oldData = $this->modelObject->find($data[$pk]);

        if (empty($oldData)) {
            throw_strack_exception("can`t found match record", ErrorCode::NO_MATCH_DATA);
        }

        $dealData = [];

        $currentModuleCode = $this->getCurrentModuleCode($currentModuleCode);
        $currentModuleFieldAttributeCheck = [];
        foreach ($data as $field => $value) {
            if (array_key_exists($field, Module::$moduleDictData['field_index_by_code'][$currentModuleCode]['fixed'])) {
                // 固定字段
                $dealData[$field] = $value;
            } else {
                // 自定义字段
                if (array_key_exists($field, Module::$moduleDictData['field_index_by_code'][$currentModuleCode]['custom'])) {
                    // 更新json字段 增加字段值的属性检查
                    if (!ImportExcelService::getInstance()->getModuleFieldAttributeValueCheck($currentModuleCode, $field, $value, $currentModuleFieldAttributeCheck)) {
                        throw_strack_exception("attribute [$field] value [$value] not allowed", ErrorCode::ATTRIBUTE_VALUE_NOT_ALLOWED);
                    }
                    if (array_key_exists('json', $dealData)) {
                        $dealData['json'][$field] = $value;
                    } else {
                        $dealData['json'] = [$field => $value];
                    }
                }
            }
        }

        if (array_key_exists('json', $dealData)) {
            $oldJson = !empty($oldData['json']) ? json_decode($oldData['json'], true) : [];
            $dealData['json'] = array_merge($oldJson, $dealData['json']);
        }

        return $dealData;
    }

    /**
     * 获得格式化数据
     * @param RelationModel $model
     * @param $data
     * @return mixed
     */
    public static function getFormatAttrData(RelationModel $model, &$data)
    {
        foreach ($data as $k => &$v) {
            // 检测属性获取器
            $method = 'get' . $model->parseName($k, 1) . 'Attr';

            if (method_exists($model, $method)) {
                $v = $model->$method($v, $data);
            }
        }
        return $data;
    }

    /**
     * 追加json字段到行数据中
     * @param $row
     * @param string $jsonFieldName
     * @param bool $remove
     * @return array
     */
    public static function appendJsonFields($row, $jsonFieldName = 'json', $remove = true)
    {
        if (!isset($row[$jsonFieldName])) {
            return $row;
        }
        if (is_string($row[$jsonFieldName])) {
            $jsonFields = json_decode($row[$jsonFieldName], true);
        } else {
            $jsonFields = $row[$jsonFieldName];
        }
        if ($remove) {
            unset($row[$jsonFieldName]);
        }
        return array_merge($jsonFields, $row);
    }
}
